﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers;
using System.IO;
using Microsoft.FxCop.Sdk;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FxCopContrib.TestHarness
{
    internal class AssemblyUnderTest
    {
        string _assemblyName;
        string _symbolsFileName;
        string _fileName;
        string _code;
        List<Assembly> _referencedAssemblies = new List<Assembly>();

        internal AssemblyUnderTest(string code)
        {
            _referencedAssemblies.Add(typeof(object).Assembly);
            _code = code;
            FindAssemblyName();
        }

        internal void FindAssemblyName()
        {
            MethodBase method = TestFrameworkHelpers.FindTestMethod();
            if (method != null)
            { 
                string baseName = method.DeclaringType.Namespace + "." + method.DeclaringType.Name + "." + method.Name;
                _fileName = baseName + ".cs";
                _assemblyName = baseName + ".dll";
                _symbolsFileName = baseName + ".pdb";
            }
        }

        internal string AssemblyName
        {
            get
            {
                if (_assemblyName == null)
                {
                    FindAssemblyName();
                }
                return _assemblyName;
            }
        }

        private void Compile()
        {
            var tree = SyntaxTree.ParseText(
                _code,
                _fileName
                );

            var compilation = Compilation.Create(
                 _assemblyName,
                options: new CompilationOptions(OutputKind.DynamicallyLinkedLibrary),
                syntaxTrees: new[] { tree },
                references: GenerateAssemblyReferences());
 
            if (File.Exists(_assemblyName)) File.Delete(_assemblyName);
            if (File.Exists(_symbolsFileName)) File.Delete(_symbolsFileName);

            using (var assemblyStream = File.Create(_assemblyName))
            using (var pdbStream = File.Create(_symbolsFileName))
            {
              EmitResult compileResult = compilation.Emit(
                  assemblyStream, 
                  pdbFileName: _symbolsFileName,
                  pdbStream: pdbStream);

              if (!compileResult.Success)
              {
                  Assert.Fail("Test Assembly Generation failed due to: {0}\r\n\r\nin:\r\n{1}", string.Join("\r\n", compileResult.Diagnostics), _code);
              }
            }
        }

        private IEnumerable<MetadataReference> GenerateAssemblyReferences()
        {
            //TODO: replace with AssemblyObjectReference when Roslyn supports it.
            return _referencedAssemblies.Distinct().Select(a => new MetadataFileReference(a.Location));
        }

        public AssemblyNode GetAssemblyNode()
        {
            string file = _assemblyName;
            if (! File.Exists(file))
            {
                Compile();
            }
            return AssemblyNode.GetAssembly(file, true, true, true);
        }

        internal void AddReference(Assembly assembly)
        {
            _referencedAssemblies.Add(assembly);
        }

        internal void AddReferences(params Type[] types)
        {
            _referencedAssemblies.AddRange(types.Select(t => t.Assembly));
        }
    }
}
